from _users import User, NonzeroReturnStatus
from _events import WarningEvent
from _receipts import ReceiptManager
import exceptions


class ShellCommandFailed(exceptions.Exception): pass


class RoleResponse(object):
    """Encapsulates the responses from the Users in a particular Role.
    There is an stdout and stderr stream for each User.  As a
    convenience, the properties stdout and stderr are exposed for
    single-User Roles (these will throw ValueErrors for multi-user Roles)"""
    
    def _get_stdout(self):
        """gets the stdout for single-User Roles
        (else, raises ValueError)"""
        keys = self.__stdout_for.keys()
        if len(keys) == 1:
            return self.stdout_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use stdout_for(fullname) instead")

    def _get_stderr(self):
        """gets the stderr for a single-user Roles
        (else, raises ValueError)"""
        keys = self.__stderr_for.keys()
        if len(keys) == 1:
            return self.stderr_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use stderr_for(fullname) instead")

    def _get_status(self):
        """gets the status for a single-user Roles
        (else, raises ValueError)"""
        keys = self.__status_for.keys()
        if len(keys) == 1:
            return self.status_for(keys[0])
        else:
            raise ValueError("more than one user participated in this response, use status_for(fullname) instead")
    
    cmd = property(lambda self: self.__cmd)
    stdout = property(_get_stdout)
    stderr = property(_get_stderr)
    status = property(_get_status)
    
    def stdout_for(self, fullname):
        """gets the stdout (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user in self.__stdout_for:
            return "".join( self.__stdout_for[user] )
        else:
            raise KeyError("the user '%s' did not participate in this response" % user)

    def stderr_for(self, fullname):
        """gets the stderr (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user in self.__stderr_for:
            return "".join( self.__stderr_for[user] )
        else:
            raise KeyError("the user '%s' did not participate in this response" % user)

    def status_for(self, fullname):
        """gets the status (as a string) for a particular User that
        participated in this response"""
        if isinstance(fullname, User):
            user = fullname
        else:
            user = User(fullname)
        if user in self.__status_for:
            return self.__status_for[user]
        else:
            raise KeyError("the user '%s' did not participate in this response" % user)
    
    def __init__(self, cmd):
        self.__cmd = cmd
        self.__stdout_for = {}
        self.__stderr_for = {}
        self.__status_for = {}
    
    def collect_from_command_stream_for_user(self, user, command_stream):
        try:
            if user not in self.__stdout_for:
                self.__stdout_for[user] = []
            if user not in self.__stderr_for:
                self.__stderr_for[user] = []
            for stdout_line, stderr_line, stdout_byte, stderr_byte in command_stream:
                self.__stdout_for[user].append(stdout_line)
                self.__stderr_for[user].append(stderr_line)
            self.__status_for[user] = 0
        except NonzeroReturnStatus, e:
            if isinstance(e, NonzeroReturnStatus):
                self.__status_for[user] = e.code
            else:
                raise


class Role(object):
    
    def __init__(self, *fullnames):
        # TODO: private variables
        self.users = []
        for fullname in fullnames:
            if isinstance(fullname, User):
                self.users.append(fullname)
            else:
                self.users.append( User(fullname) )
    
    def run(self, cmd, ignore_failure = False):
        """runs a shell command for this Role"""
        role_response = RoleResponse(cmd)
        for user in self.users:
            command_stream = user.create_command_stream(cmd)
            # TODO: give this stream to the ReceiptManager on another thread as well (then join that thread)
            # TODO: allow parallelizable execution (this is serialized right now)
            role_response.collect_from_command_stream_for_user(user, command_stream)
            status = role_response.status_for(user)
            if not ignore_failure and status is not 0:
                raise ShellCommandFailed("'%(cmd)s' failed for %(user)s with exit status %(status)s" % dict(
                    cmd = cmd,
                    user = user,
                    status = status
                ))
        return role_response
    
    def _delegate_call_to_all_user_strategies(self, method_name, *args, **kwargs):
        for user in self.users:
            getattr(user, method_name)(*args, **kwargs)
    
    def pull(self, local_filename, remote_filename):
        """copies a file from the local machine to the
        (likely remote) destination """
        return self._delegate_call_to_all_user_strategies("pull", local_filename, remote_filename)

    def expect_password_for(self, pattern):
        """adds a password expectation that the user must respond to"""
        return self._delegate_call_to_all_user_strategies("expect_password_for", pattern)

    def expect_input_for(self, pattern):
        """adds an input expectation that the user must respond to"""
        return self._delegate_call_to_all_user_strategies("expect_input_for", pattern)

    def respond_with(self, pattern, response):
        """adds an expectation that is automatically responded to"""
        return self._delegate_call_to_all_user_strategies("respond_with", pattern, response)

    def respond_with_password(self, pattern, response):
        """adds a password expecation that is automatically responded to"""
        return self._delegate_call_to_all_user_strategies("respond_with_password", pattern, response)
    
    def stop_expecting(self, pattern):
        """tells the strategy to stop expecting input for a
        particular pattern (password or input)"""
        return self._delegate_call_to_all_user_strategies("stop_expecting", pattern)
    
    def stop_responding_to(self, pattern):
        """tells the strategy to stop automatically responding for
        a particular pattern (password or input)"""
        return self._delegate_call_to_all_user_strategies("stop_responding_to", pattern)
            
